<?php
/**
* phpCan - http://idc.anavallasuiza.com/
*
* phpCan is released under the GNU Affero GPL version 3
*
* More information at license.txt
*/

defined('ANS') or die();


class Admin {
	private $Debug;
	private $Errors;

	public $Data;
	public $settings = array();

	private $format_errors;


	/**
	 * public function __construct (void)
	 *
	 */
	public function __construct () {
		global $Debug, $Errors, $Config;

		$this->Debug = $Debug;
		$this->Errors = $Errors;
		$this->settings['tables'] = $Config->admin_tables;
		$this->settings['tables_info'] = $Config->admin_tables_info;
		$this->settings['relations'] = $Config->admin_relations;
		$this->settings['languages'] = (array)$Config->languages['availables'];

		$this->Data = new \Data\Data();

		$this->setLanguage();
	}


	/**
	 * public function view ($connection, $table, $id)
	 *
	 */
	public function view ($connection, $table, $id) {
		global $Vars;

		if (!($function = $this->settings['tables_info'][$connection][$table]['view'])) {
			$Vars->message('The table %s can be viewed', $table);

			return false;
		}

		if (is_callable($function) || (is_string($function) && function_exists($function)) || (is_array($function) && is_object($function[0]) && method_exists($function[0], $function[1]))) {
			return call_user_func($function, $this->Data, $table, $id);
		} else {
			$Vars->message('The view function for the table %s is not callable', $table);
		}
	}


	/**
	 * public function setLanguage ([$language])
	 *
	 */
	public function setLanguage ($language = '') {
		global $Vars;

		if ($language == 'all') {
			$this->settings['language'] = 'all';
			$Vars->deleteCookie('phpcan_admin_data_language');
		} else if ($language && array_key_exists($language, $this->settings['languages'])) {
			$this->settings['language'] = $language;
			$Vars->setCookie('phpcan_admin_data_language', $language, 3600*24);
		} else if ($language = $Vars->getCookie('phpcan_admin_data_language')) {
			$this->settings['language'] = $language;
		} else {
			$this->settings['language'] = 'all';
		}

		$this->Data->language($this->settings['language']);

		return true;
	}


	/**
	 * public function getLanguages ()
	 *
	 */
	public function getLanguages () {
		$languages = array_keys($this->settings['languages']);
		array_unshift($languages, 'all');
		
		return $languages;
	}


	/**
	 * public function getLanguage ()
	 *
	 */
	public function getLanguage () {
		return $this->settings['language'];
	}


	/**
	 * public function setConnection (string $connection)
	 *
	 * return boolean
	 */
	public function setConnection ($connection) {
		$Data = getDatabaseObject($connection);
		$this->Data->setDatabase($Data, $this->settings['tables'][$connection], $this->settings['relations'][$connection]);

		return $this->Data->getDatabase() ? true : false;
	}

	/**
	 * public function checkTable ($table)
	 *
	 * return boolean
	 */
	public function checkTable ($table) {
		return $this->Data->tableExists($table);
	}


	/**
	* public function info (string $type, string $table, [string $format])
	*
	* return string
	*/
	public function info ($type, $table, $format = '') {
		if (!$format) {
			if (!$this->settings['tables_info'][$table][$type]) {
				return ($type == 'name') ? format($table) : '';
			}

			return $this->settings['tables_info'][$table][$type];
		}

		if (!$this->settings['tables_info'][$table]['formats'][$format][$type]) {
			return ($type == 'name') ? format($format) : '';
		}

		return $this->settings['tables_info'][$table]['formats'][$format][$type];
	}


	/**
	 * public function selectMenuTables ([string $store])
	 *
	 * return array
	 */
	public function selectMenuTables ($store) {
		global $Vars;

		$menu_tables = array();

		foreach ($this->settings['tables'] as $connection => $tables) {
			$tables = array_keys($tables);
			sort($tables);

			foreach ($tables as $table) {
				$menu_tables[] = array(
					'name' => $this->info('name', $table),
					'description' => $this->info('description', $table),
					'table' => $table,
					'connection' => $connection,
					'url' => path($connection, $table, 'list')
				);
			}
		}

		$Vars->setData($store, $menu_tables);

		return $menu_tables;
	}


	/**
	 * private function getQuery (array $options)
	 *
	 * return array
	 */
	private function getQuery ($options) {
		$query = array(
			'table' => $options['table'],
			'fields' => $options['fields'] ? $options['fields'] : '*',
			'add_tables' => $options['add_tables'],
			'sort' => $options['sort'] ? $options['sort'] : 'id',
			'sort_direction' => $options['sort_direction'] ? $options['sort_direction'] : 'DESC'
		);

		if ($options['search']) {
			$Search = new \data\Search($this->Data);

			$query_search = $Search->getQuery(array(
				'text' => $options['search'],
				'operators' => 'all',
				'where' => array(
					'table' => $options['table'],
					'fields' => '*'
				)
			));

			$query = $this->Data->mergeQuery($query_search, $query);
		}

		return $query;
	}


	/**
	 * public function selectEdit (array $options, [string $store])
	 *
	 * return array
	 */
	public function selectEdit ($options, $store = '') {
		$query = $this->getQuery($options);

		$query['conditions']['id'] = $options['ids'];
		$query['limit'] = count($options['ids']);
		$query['rows'] = true;
		$query['exit_format'] = 'form';

		$result = $this->Data->select($query);

		if (!$result) {
			return false;
		}

		$result = $this->queryToArray($query, $result, false, 'edit');
		$result = $this->explodeResult($options['table'], $result[$options['table']], $this->format_errors);
		$result = $this->getFormResult($result, true);

		if ($options['relation'] && $options['relation_id']) {
			$relation_vars = array(
				$result[0]['varname'].'[tables]['.$options['relation'].'][0][id]' => $options['relation_id'],
				$result[0]['varname'].'[tables]['.$options['relation'].'][0][action]' => 'relate'
			);

			foreach ($result as &$row) {
				$row['vars'] = $relation_vars;
			}
		}

		if ($store) {
			global $Vars;

			$Vars->setData($store, $result);
		}

		return $result;
	}


	/**
	 * public function selectNew (array $options, [string $store])
	 *
	 * return array
	 */
	public function selectNew ($options, $store = '') {
		$query = $this->getQuery($options);

		$result = $this->queryToArray($query, array(), true, 'edit');
		$result = $this->explodeResult($options['table'], $result[$options['table']], $this->format_errors);
		$result = $this->getFormResult($result);

		if ($options['relation'] && $options['relation_id']) {
			$relation_vars = array(
				$result[0]['varname'].'[tables]['.$options['relation'].'][0][id]' => $options['relation_id'],
				$result[0]['varname'].'[tables]['.$options['relation'].'][0][action]' => 'relate'
			);

			foreach ($result as &$row) {
				$row['vars'] = $relation_vars;
			}
		}

		if ($store) {
			global $Vars;

			$Vars->setData($store, $result);
		}

		return $result;
	}


	/**
	 * public function selectList (array $options, [string $store])
	 *
	 * return array
	 */
	public function selectList ($options, $store = '') {
		$query = $this->getQuery($options);

		$query['limit'] = 20;

		$query['pagination'] = array(
			'map' => 20,
			'page' => $options['page']
		);

		$result = $this->Data->select($query);

		$processed_result = $this->queryToArray($query, $result, true, 'edit');
		$processed_result[$options['table']] = $this->explodeResult($options['table'], $processed_result[$options['table']]);

		$list = array(
			'head' => $this->getHeadResult($processed_result[$options['table']]),
			'body' => $result ? $this->getBodyResult($processed_result[$options['table']]) : array()
		);

		if ($store) {
			global $Vars;

			$Vars->setData($store, $list);
		}

		return $list;
	}


	/**
	 * public function selectRelations (array $options, [string $store])
	 *
	 * return array
	 */
	public function selectRelations ($options, $store = '') {
		$query = $this->getQuery($options);

		$query['limit'] = 20;

		$query['pagination'] = array(
			'map' => 20,
			'page' => $options['page']
		);

		if (!$options['all']) {
			$query['conditions'][$options['relation'].'.id'] = $options['relation_id'];
		}

		$options['relation'] = $this->Data->tableArray($options['relation']);

		$query['add_tables']['phpcan_related'] = array(
			'table' => $options['relation']['realname'],
			'fields' => 'id',
			'name' => $options['relation']['name'],
			'direction' => $options['relation']['direction'],
			'conditions' => array(
				'id' => $options['relation_id']
			),
			'limit' => 1
		);

		$result = $this->Data->select($query);

		$relations = array();

		foreach ($result as $num_row => $row) {
			$relations[$num_row] = $row['phpcan_related'] ? true : false;
			unset($result[$num_row]['phpcan_related']);
		}

		unset($query['add_tables']['phpcan_related']);

		$processed_result = $this->queryToArray($query, $result, true, 'edit');
		$processed_result[$options['table']] = $this->explodeResult($options['table'], $processed_result[$options['table']]);

		$list = array(
			'head' => $this->getHeadResult($processed_result[$options['table']]),
			'body' => $result ? $this->getBodyResult($processed_result[$options['table']]) : array()
		);

		foreach ($relations as $num_row => $row) {
			$list['body'][$num_row]['related'] = $row;
		}

		if ($store) {
			global $Vars;
			
			$Vars->setData($store, $list);
		}

		return $list;
	}


	/**
	 * public function saveEdit (array $array)
	 *
	 * return bool/int
	 */
	public function saveEdit ($array) {
		$query = $this->arrayToQuery($array);

		$this->format_errors = array();

		$result = $this->Data->save($query, true);

		$this->format_errors = $this->Errors->get();

		return $result;
	}


	/**
	 * public function delete (array $config)
	 *
	 * return bool/int
	 */
	public function delete ($config) {
		$query = array(
			'table' => $config['table'],
			'conditions' => array(
				'id' => $config['id']
			),
			'limit' => count($config['id'])
		);

		return $this->Data->delete($query);
	}


	/**
	 * public function saveRelation (array $config)
	 *
	 * return bool/int
	 */
	public function saveRelation ($config) {
		$relation = $this->Data->tableArray($config['relation']);
		$table = $this->Data->getTable($config['table']);

		$relation_object = $table->getRelation($relation['realname'], $relation['name'], $relation['direction']);

		if (!$relation_object) {
			$this->Debug->error('db', 'There is not relation between %s and %s', $config['table'], $config['relation']);

			return false;
		}

		$query = array(
			'name' => $relation_object->settings['name'],
			'tables' => array(
				array(
					'table' => $relation_object->settings['tables'][0],
					'direction' => $relation_object->settings['direction'][0],
					'conditions' => array(
						'id' => $config['id']
					)
				),
				array(
					'table' => $relation_object->settings['tables'][1],
					'direction' => $relation_object->settings['direction'][1],
					'conditions' => array(
						'id' => $config['relation_id']
					)
				)
			)
		);

		if ($config['action'] == 'relate') {
			return $this->Data->relate($query);
		}

		return $this->Data->unrelate($query);
	}


	/**
	 * private function getFormResult (array $result, [bool $all_relations])
	 *
	 * return array
	 */
	private function getFormResult ($result, $all_relations = false) {
		global $Templates;

		$table = $this->Data->getTable($result[0]['table']);

		$relations = $all_relations ? $table->getRelations() : array();

		$format_templates = $this->getFormatTemplates($result[0]['data'], 'edit');

		foreach ($result as $num_row => &$row) {
			foreach ($row['data'] as &$field) {
				$field = array(
					'templates' => $format_templates[$field['format']],
					'data' => $field
				);
			}

			foreach ($relations as $relation_name => $relation) {
				if ($row['tables'][$relation_name] || $relation->settings['auto']) {
					continue;
				}

				$row['data']['relation '.$relation_name] = array(
					'templates' => array(
						'index' => 'relations/default/index.php',
						'content' => 'relations/default/content.php'
					),
					'data' => array(
						'table' => $relation->settings['tables'][1],
						'name' => $relation->settings['name'],
						'varname' => $row['varname'].'[tables]['.$this->Data->tableString($relation->settings['tables'][0], '', $relation->settings['name'], $relation->settings['direction'][0]).']',
						'direction' => $relation->settings['direction'][1],
						'relation' => $this->Data->tableString($relation->settings['tables'][0], '', $relation->settings['name'], $relation->settings['direction'][1]),
						'unique' => $relation->unique ? false : true,
						'id' => $row['id'],
						'title' => format($relation_name),
						'description' => __('%s %s related with %s %s #%s', $relation->settings['tables'][1], $relation->settings['direction'][0], $relation->settings['tables'][0], $relation->settings['name'], $row['id'])
					)
				);
			}

			foreach ((array)$row['tables'] as $added_table => $added_table_result) {
				$row['tables'][$added_table] = $this->getFormResult($added_table_result, $all_relations);
			}
		}

		return $result;
	}


	/**
	 * private function getHeadResult (array $result)
	 *
	 * return array
	 */
	private function getHeadResult ($result) {
		$result = current($result);

		$head = array(array(
			'table' => format($result['table']),
			'cols' => count($result['data'])
		));

		foreach ($result['data'] as $name => $info) {
			$head[0]['data'][] = array(
				'format' => $info['format'],
				'title' => $info['title'],
				'description' => $info['description']
			);
		}

		foreach ((array)$result['tables'] as $added_table => $added_table_result) {
			$head = array_merge($head, $this->getHeadResult($added_table_result));
		}

		return $head;
	}


	/**
	 * private function getFormatTemplates (array $result, string $template)
	 *
	 * return array
	 */
	private function getFormatTemplates ($result, $template) {
		global $Templates;

		$format_templates = array();

		foreach ($result as $info) {
			if ($format_templates[$info['format']]) {
				continue;
			}

			$format_templates[$info['format']] = array(
				'index' => $Templates->exists('formats/'.$info['format'].'/'.$template.'/index.php') ? 'formats/'.$info['format'].'/'.$template.'/index.php' : 'formats/default/'.$template.'/index.php',
				'content' => $Templates->exists('formats/'.$info['format'].'/'.$template.'/content.php') ? 'formats/'.$info['format'].'/'.$template.'/content.php' : 'formats/default/'.$template.'/content.php'
			);
		}

		return $format_templates;
	}


	/**
	 * private function getBodyResult (array $result)
	 *
	 * return array
	 */
	private function getBodyResult ($result) {
		$body = array();

		$format_templates = $this->getFormatTemplates($result[0]['data'], 'list');

		foreach ($result as $num_row => $row) {
			$body[$num_row] = array(
				'table' => $row['table'],
				'id' => $row['id'],
				'rows' => $row['rows'],
				'view' => $row['view']
			);

			foreach ($row['data'] as $name => $info) {
				$info['templates'] = $format_templates[$info['format']];

				$body[$num_row]['data'][$name][0] = $info;
			}

			foreach ((array)$row['tables'] as $added_table => $added_table_result) {
				$body[$num_row]['data'] += $this->_getBodyResult($added_table_result);
			}
		}

		return $body;
	}


	/**
	 * private function _getBodyResult (array $result)
	 *
	 * return array
	 */
	private function _getBodyResult ($result) {
		$result_body = array();

		foreach ($result as $num_row => $row) {
			foreach ($row['data'] as $name => $info) {
				$result_body[$name][$num_row] = $info;
			}

			foreach ((array)$row['tables'] as $added_table => $added_table_result) {
				$result_body[$num_row] = array_merge($result_body[$num_row], $this->_getBodyResult($added_table_result));
			}
		}

		return $result_body;
	}


	/**
	 * private function explodeResult (string $table, array $result, [array $errors])
	 *
	 * return array
	 */
	private function explodeResult ($table, $result, $errors = array()) {
		$rows = 1;

		foreach ($result as $num_row => &$row) {
			$errors_row = $errors[$row['table']][$num_row];
			
			$row['view'] = ($this->settings['tables_info'][$table]['view']) ? true : false;

			foreach ($row['tables'] as $added_table => &$added_table_result) {
				$added_table_result = $this->explodeResult($added_table, $added_table_result, $errors[$row['table']][$added_table]);

				$count = count($added_table_result);

				if ($count > $rows) {
					$rows = $count;
				}
			}

			$result_data = array();

			foreach ($row['data'] as $format_name => $format_data) {
				foreach ($format_data['fields'] as $language => $fields) {
					$result_data[$table.' '.$format_name.' '.$language] = array(
						'table' => $row['table'],
						'id' => $row['id'],
						'format' => $format_data['format'],
						'field' => $format_name,
						'title' => format($this->info('name', $row['table'], $format_name), $language),
						'description' => $this->info('description', $row['table'], $format_name),
						'language' => $language,
						'varname' => $format_data['varname'].'['.($language ? $language : 0).']',
						'rows' => $rows,
						'data' => $fields,
						'error' => $errors_row[$format_name][$language],
						'view' => $row['view']
					);
				}
			}

			$row['data'] = $result_data;
			$row['rows'] = $rows;
		}

		return $result;
	}


	/**
	 * private function queryToArray (array $query, array $result, [boolean $empty_values], [string $varname])
	 *
	 * Group the result of a select query by table, format, language and fields
	 *
	 * return array
	 */
	private function queryToArray ($query, $result, $empty_values = false, $varname = '') {
		$table = $this->Data->getTable($query['table']);

		$group_table_name = $this->Data->tableString($query['table'], $query['name'], $query['direction']);
		$group_result = array();
		$tmp_group_result = array();

		if ($empty_values && !$result) {
			$result = array($table->getDefaultValues());
		}

		foreach ($table->formats as $field => $format) {
			if ($format->format == 'id_relation') {
				continue;
			}

			foreach ($result as $num_row => $row) {
				$tmp_group_result[$num_row][$field] = $row[$field];
			}
		}

		$tmp_group_result = $table->explodeData($tmp_group_result, $this->settings['language']);

		foreach ($tmp_group_result as $num_row => $row) {
			$row_varname = $varname.'['.$group_table_name.']['.$num_row.']';

			//Row
			$group_result[$num_row] = array(
				'id' => $row['id'][''][''],
				'table' => $query['table'],
				'tables' => array(),
				'data' => array(),
				'varname' => $row_varname
			);

			$group_result[$num_row]['action'] = $group_result[$num_row]['id'] ? 'update' : 'insert';

			//Data
			foreach ($row as $format_name => $fields) {
				$format = $table->getFormat($format_name);

				$group_result[$num_row]['data'][$format_name] = array(
					'varname' => $row_varname.'[data]['.$format_name.']',
					'format' => $format->format,
					'fields' => $fields
				);
			}

			//Added tables
			foreach ((array)$query['add_tables'] as $added_name => $added_table) {
				$added_name = $this->Data->addedName($added_name, $added_table['table'], $added_table['name'], $added_table['direction']);

				if (!$empty_values && !$result[$num_row][$added_name]) {
					continue;
				}

				$group_result[$num_row]['tables'] += $this->queryToArray($added_table, $result[$num_row][$added_name], $empty_values, $row_varname.'[tables]');
			}
		}

		return array($group_table_name => $group_result);
	}


	/**
	 * private function arrayToQuery (array $result)
	 *
	 * return array
	 */
	private function arrayToQuery ($result) {
		$query = array();

		foreach ($result as $table => $rows) {
			$table_info = $this->Data->tableArray($table);
			$table = $this->Data->getTable($table_info['realname']);

			foreach ($rows as $row) {
				$action = $row['action'];

				if (!$action) {
					continue;
				}

				$row['data'] = current($table->implodeData(array($row['data'])));

				$options = array(
					'data' => $row['data'],
					'table' => $table_info['realname'],
					'name' => $table_info['name'],
					'direction' => $table_info['directon'],
				);

				switch ($action) {
					case 'relate':
					case 'unrelate':
						$options['conditions']['id'] = $row['id'];
						$options['no_duplicate'] = true;

						if (!is_array($row['id']) || count($row['id']) == 1) {
							$options['limit'] = 1;
						}

						break;

					case 'update':
					case 'delete':
						$options['conditions']['id'] = $row['data']['id'];
						unset($options['data']['id']);

						if (!is_array($row['id']) || count($row['id']) == 1) {
							$options['limit'] = 1;
						}

						break;
				}

				if ($row['tables']) {
					$options += $this->arrayToQuery($row['tables']);
				}

				$query[$action][] = $options;
			}
		}

		return $query;
	}
}
?>
